Skip to content

[linter-miner] feat(linters): add bytesbufferstring linter — flag string(buf.Bytes()) in favour of buf.String()#45301

Merged
pelikhan merged 5 commits into
mainfrom
linter-miner/bytesbufferstring-69f1bb94c2dc7fc6
Jul 14, 2026
Merged

[linter-miner] feat(linters): add bytesbufferstring linter — flag string(buf.Bytes()) in favour of buf.String()#45301
pelikhan merged 5 commits into
mainfrom
linter-miner/bytesbufferstring-69f1bb94c2dc7fc6

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

test

Generated by PR Description Updater for #45301 · 47 AIC · ⌖ 4.95 AIC · ⊞ 4.7K ·

Reports string(buf.Bytes()) calls where buf is *bytes.Buffer,
suggesting buf.String() instead. The .String() method avoids an
unnecessary allocation by reading the underlying byte slice directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added automation cookie Issue Monster Loves Cookies! go-linters labels Jul 13, 2026
@pelikhan pelikhan marked this pull request as ready for review July 13, 2026 21:22
Copilot AI review requested due to automatic review settings July 13, 2026 21:22
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Design Decision Gate 🏗️ completed the design decision gate check.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a custom Go analyzer that replaces string(buf.Bytes()) with buf.String().

Changes:

  • Implements and registers the bytesbufferstring analyzer.
  • Adds diagnostics, suppression handling, and automatic fixes.
  • Adds analysistest fixtures and golden output.
Show a summary per file
File Description
cmd/linters/main.go Registers the analyzer.
pkg/linters/bytesbufferstring/bytesbufferstring.go Implements detection and fixes.
pkg/linters/bytesbufferstring/bytesbufferstring_test.go Runs analyzer tests.
pkg/linters/bytesbufferstring/testdata/src/bytesbufferstring/bytesbufferstring.go Provides test cases.
pkg/linters/bytesbufferstring/testdata/src/bytesbufferstring/bytesbufferstring.go.golden Defines expected fixes.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Medium

if receiverType == nil {
return
}
if !isBytesBufferPtr(receiverType) {

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: bytesbufferstring linter The implementation is well-structured and follows existing linter conventions closely. The type-checking via isBytesBufferPtr correctly handles both bytes.Buffer value and *bytes.Buffer pointer receivers, the nolint suppression works, and the golden-file test covers the key cases. One non-blocking issue found: the analyzer registration order in multichecker.Main(...) (see inline comment) — bytesbufferstring should appear before bytescomparestring to match the alphabetical order used elsewhere in that list and already used in the import block.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 27.7 AIC · ⌖ 6.98 AIC · ⊞ 4.8K

Comment thread cmd/linters/main.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 30.6 AIC · ⌖ 4.49 AIC · ⊞ 6.6K
Comment /matt to run again

})
})

return nil, nil

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Missing test for a named type that embeds bytes.BufferisBytesBufferPtr only checks obj.Name() == "Buffer", so a type like type MyBuf bytes.Buffer (or an interface wrapping it) would silently pass through unchecked.

💡 Suggested test fixture addition

Add to testdata/src/bytesbufferstring/bytesbufferstring.go:

type MyBuffer bytes.Buffer

func GoodStringConversionAliasedType() string {
    var buf MyBuffer
    // string(buf.Bytes()) should NOT fire — MyBuffer is not bytes.Buffer
    _ = buf
    return ""
}

The intent is to confirm the linter does not fire on types that merely share a name or embed the buffer, preventing potential future false positives if isBytesBufferPtr is extended.

@copilot please address this.

TextEdits: []analysis.TextEdit{{
Pos: call.Pos(),
End: call.End(),
NewText: []byte(receiverText + ".String()"),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] Function name isBytesBufferPtr is misleading — the function also handles value receivers (bytes.Buffer, not just *bytes.Buffer), as the comment confirms. Consider renaming to isBytesBuffer or isBytesBufferType to match the actual semantics.

@copilot please address this.

func BadStringOfBytesCall() string {
var buf bytes.Buffer
buf.WriteString("hello")
return string(buf.Bytes()) // want `string\(buf\.Bytes\(\)\) can be simplified to buf\.String\(\)`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] Missing test case for a chained or indirect receiver: getBuffer().Bytes() where getBuffer() returns *bytes.Buffer. This is a realistic code pattern and confirming it fires (or does not fire intentionally) clarifies the intended scope.

💡 Suggested addition
func getBuffer() *bytes.Buffer { return &bytes.Buffer{} }

func BadStringOfBytesCallIndirect() string {
    return string(getBuffer().Bytes()) // want `string\(getBuffer\(\)\.Bytes\(\)\) can be simplified to getBuffer\(\)\.String\(\)`
}

If the current astutil.NodeText call returns the full receiver text including the call expression, this will already work — adding a fixture case makes that explicit.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter logic is correct: type resolution via pass.TypesInfo, pointer-unwrapping in isBytesBufferPtr, and the nolint suppression path all work as expected. No false-positive or false-negative correctness bugs found.

Non-blocking observations

Misleading helper name

isBytesBufferPtr handles both *bytes.Buffer and bytes.Buffer (value). The name implies pointer-only, which will confuse the next person reading if !isBytesBufferPtr(receiverType) for a value-typed receiver. Rename to isBytesBufferType.

Test fixture coverage

Only simple local-variable receivers (var buf bytes.Buffer, buf := &bytes.Buffer{}) are exercised. Struct-field and chained receivers (w.buf.Bytes()) are not tested. The silent if receiverText == "" guard means failures in astutil.NodeText for more complex nodes would silently drop diagnostics rather than surface as test failures.

🔎 Code quality review by PR Code Quality Reviewer · 32.6 AIC · ⌖ 4.57 AIC · ⊞ 5.4K
Comment /review to run again

Comments that could not be inline-anchored

pkg/linters/bytesbufferstring/bytesbufferstring.go:137

Misleading function name: isBytesBufferPtr also accepts bytes.Buffer (non-pointer), which the comment confirms, but the name implies pointer-only.

<details>
<summary>💡 Suggested fix</summary>

Rename to isBytesBufferType (or isBytesBuffer) to match what it actually does:

// isBytesBufferType reports whether t is *bytes.Buffer or bytes.Buffer.
func isBytesBufferType(t types.Type) bool {

A maintainer reading if !isBytesBufferPtr(receiverType) would reasonably assume va…

pkg/linters/bytesbufferstring/testdata/src/bytesbufferstring/bytesbufferstring.go:34

Test coverage gap: no test case for struct-field or method-chained receivers, which are common real-world patterns.

<details>
<summary>💡 Suggested additions</summary>

Add to the test fixture to verify the linter fires and auto-fixes on more complex receiver expressions:

type Wrapper struct {
    buf bytes.Buffer
}

func BadStructFieldReceiver(w *Wrapper) string {
    return string(w.buf.Bytes()) // want `string\(w\.buf\.Bytes\(\)\) can be simplified to w\.buf\.String\(\)`
}

W…

@github-actions

Copy link
Copy Markdown
Contributor Author

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 1 test: 1 design, 0 implementation, 0 violations.

📊 Metrics (1 test)
Metric Value
Analyzed 1 (Go: 1, JS: 0)
✅ Design 1 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 1 (100%)
Duplicate clusters 0
Inflation No (0.13:1 ratio)
🚨 Violations 0

Test Classification

Test File Classification Notes
TestAnalyzer bytesbufferstring_test.go:13 ✅ Design Validates linter correctly detects string(buf.Bytes()) and suggests buf.String() fix

Detailed Analysis

TestAnalyzer — Uses analysistest.RunWithSuggestedFixes to verify the linter against 5 testdata scenarios:

  1. Bad pattern (stack buffer): detects and suggests fix
  2. Bad pattern (pointer buffer): detects and suggests fix
  3. Nolint suppression: correctly suppresses diagnostics
  4. Correct pattern: no false positives on buf.String() calls
  5. Type coverage: excludes non-buffer string conversions

The test file has proper //go:build !integration tag, uses standard analysis framework (no forbidden mocks), and maintains healthy 0.13:1 test:code ratio with comprehensive testdata coverage.

Verdict

Passed. 0% implementation tests (threshold: 30%). No violations detected.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 17.1 AIC · ⌖ 8.57 AIC · ⊞ 6.9K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 100/100. 0% implementation tests (threshold: 30%). No violations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor Author

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (206 new lines in pkg/ and cmd/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/45301-bytesbufferstring-linter-flag-string-of-buf-bytes.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-45301: Add bytesbufferstring Linter

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 45301-bytesbufferstring-linter-flag-string-of-buf-bytes.md for PR #45301).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 48.8 AIC · ⌖ 12.7 AIC · ⊞ 8.5K ·
Comment /review to run again

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address any unresolved review feedback, and rerun checks once the branch is up to date.

Generated by 👨‍🍳 PR Sous Chef · 6.38 AIC · ⌖ 9.66 AIC · ⊞ 4.2K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 13, 2026 22:11
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…*bytes.Buffer

The rewrite string(buf.Bytes()) → buf.String() is not semantics-preserving
when buf is a nil *bytes.Buffer: the former panics while the latter returns
"<nil>". Restrict the linter to only flag bytes.Buffer value receivers.

- Rename isBytesBufferPtr → isBytesBufferValue; no longer unwraps pointers
- Update Analyzer.Doc, package doc comment, and inline comments
- Remove the want annotation from BadStringOfBytesCallPtr test case
- Add NilPointerBuf fixture showing nil *bytes.Buffer is not flagged
- Update golden file to match new fixture

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

@copilot please run the pr-finisher skill, address any unresolved review feedback, and rerun checks once the branch is up to date.

...

Addressed the nil-pointer semantics issue in commit fix(linters/bytesbufferstring). The linter now only flags value receivers (bytes.Buffer), not pointer receivers (*bytes.Buffer). The rewrite string(buf.Bytes()) → buf.String() is not semantics-preserving for nil pointers (the former panics; the latter returns "<nil>"), so pointer cases are excluded entirely. A NilPointerBuf fixture was added to confirm the nil case is not flagged.

Copilot AI requested a review from gh-aw-bot July 13, 2026 22:17
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

pr-sous-chef
@copilot please run the pr-finisher skill, address these unresolved review comments, update the PR body with the ADR link, and rerun checks once the branch is up to date:

Run: https://github.com/github/gh-aw/actions/runs/29289965782

Generated by 👨‍🍳 PR Sous Chef · 10.9 AIC · ⌖ 3.94 AIC · ⊞ 7.5K ·
Comment /souschef to run again

…rect-receiver fixtures

- cmd/linters/main.go: register bytesbufferstring.Analyzer before bytescomparestring.Analyzer (alphabetical order)
- testdata: add wrappedBuffer (embedded bytes.Buffer) fixture — confirms linter does not fire on non-bytes.Buffer named types
- testdata: add getBufferPtr() indirect pointer-receiver fixture — confirms linter does not fire when receiver is *bytes.Buffer
- golden: update to match new no-op fixtures

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

pr-sous-chef
@copilot please run the pr-finisher skill, address these unresolved review comments, update the PR body with the ADR link, and rerun checks once the branch is up to date:

  • copilot-pu...

Addressed in commit e514895:

  • Alphabetical order (cmd/linters/main.go): bytesbufferstring.Analyzer now registered before bytescomparestring.Analyzer.
  • Named-type fixture: added wrappedBuffer (struct embedding bytes.Buffer) — confirms the linter does not fire on non-bytes.Buffer named types (receiver-type path check enforces bytes.Buffer exactly).
  • Indirect/chained pointer-receiver fixture: added getBufferPtr() returning *bytes.Buffer — confirms pointer receivers are not flagged.
  • isBytesBufferPtr rename: already resolved in the previous commit (e623ff4) — renamed to isBytesBufferValue.
  • ADR link in PR body: the REST API is firewall-blocked from this agent; please add the following line to the PR body manually: ADR: [ADR-45301: Add bytesbufferstring Linter](docs/adr/45301-bytesbufferstring-linter-flag-string-of-buf-bytes.md)

CI needs a human re-trigger (close/reopen or workflow_dispatch) before merge.

@pelikhan pelikhan merged commit ee159b2 into main Jul 14, 2026
38 checks passed
@pelikhan pelikhan deleted the linter-miner/bytesbufferstring-69f1bb94c2dc7fc6 branch July 14, 2026 02:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automation cookie Issue Monster Loves Cookies! go-linters

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants